﻿/*
Copyright (C) 2010 INOUE Hiroyuki

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AC = System.Security.AccessControl;
using NTAccount = System.Security.Principal.NTAccount;

namespace CodePlex.Dombly.DiskUsage.BusinessLogic
{

    /// <summary>
    /// Provides the properties for information of files.
    /// </summary>
    public class Directory : FileSystem<DirectoryInfo>, IDirectory
    {
        #region IFileSystem implementations

        #region DirectoryCount
        /// <summary>
        /// Gets the the count of directories beneath the directory, 
        /// including itself and all the subdirectories.
        /// </summary>
        public override int DirectoryCount
        {
            get
            {
                if (!this.Scanned.HasValue) this.Aggregate();
                return _DirectoryCount;
            }
        }
        private int _DirectoryCount;
        #endregion

        #region FileCount
        /// <summary>
        /// Gets the count of files beneath the directory and all the 
        /// subdirectories.
        /// </summary>
        public override int FileCount
        {
            get
            {
                if (!this.Scanned.HasValue) this.Aggregate();
                return this._FileCount;
            }
        }
        private int _FileCount;
        #endregion

        #region FileSize
        /// <summary>
        /// Gets the summation of file sizes of all the files beneath the 
        /// directory and all the subdirectories, in bytes.
        /// </summary>
        public override long FileSize
        {
            get
            {
                if (!this.Scanned.HasValue) this.Aggregate();
                return this._FileSize;
            }
        }
        private long _FileSize;
        #endregion

        #region Owner
        /// <summary>
        /// Gets the owner of the directory.
        /// </summary>
        public override string Owner
        {
            get
            {
                var ac = new AC.DirectorySecurity(
                    this._Info.FullName,
                    AC.AccessControlSections.Owner);
                var id = (NTAccount)ac.GetOwner(typeof(NTAccount));
                return id.ToString();
            }
        }
        #endregion

        #endregion

        #region IDirectory implementations

        #region Depth
        /// <summary>
        /// Gets the depth from the root directory where the scan started.
        /// The root directory is defined to be depth zero.
        /// </summary>
        public int Depth { get { return this._Depth; } }
        private readonly int _Depth;
        #endregion

        #endregion

        #region constructors

        private Directory(DirectoryInfo info, IDirectory parent)
            : base(info, parent)
        {
            if (DirectoryScanning != null)
                DirectoryScanning(this, new EventArgs());
         
            if (DirectoryScanCompleted != null)
                this.ScanCompleted += DirectoryScanCompleted;
            this._Depth = parent == null? 0 : parent.Depth + 1;
        }

        /// <summary>
        /// Initializes a root directory instance.
        /// </summary>
        /// <param name="path">Full path to the root directory.</param>
        public Directory(string path)
            : this(new DirectoryInfo(path), null)
        { }

        #endregion

        #region properties

        #region Directories
        /// <summary>
        /// Gets the list of directories just beneath this directory.
        /// </summary>
        public IList<Directory> Directories
        {
            get
            {
                return this._Directories
                     = this._Directories
                    ?? this._Info
                           .GetDirectories()
                           .Select(i => new Directory(i, this))
                           .OrderBy(d => d.FullName)
                           .ToList();
            }
        }
        private IList<Directory> _Directories;
        #endregion

        #region Files
        /// <summary>
        /// Gets the list of files just beneath this directory.
        /// </summary>
        public IList<File> Files
        {
            get
            {
                return this._Files
                     = this._Files
                    ?? this._Info
                           .GetFiles()
                           .Select(i => new File(i, this))
                           .ToList();
            }
        }
        private IList<File> _Files;
        #endregion

        #region IsCleared
        /// <summary>
        /// Gets a value indicating whether directories/files lists are cleared.
        /// </summary>
        public bool IsCleared { get; private set; }
        #endregion

        #endregion

        #region methods

        /// <summary>
        /// Collects information of every directory beneath this directory.
        /// </summary>
        /// <param name="threshold">
        /// Threshold of depth to be aggregated sequentially. 
        /// (e.g. reporting depth)
        /// </param>
        public void Aggregate(int threshold)
        {
            _AggregateThreshold = threshold;
            this.Aggregate();
        }
        private static int _AggregateThreshold;
        private void Aggregate()
        {
            if (this.Scanned.HasValue) return;
            // subitems to aggregate
            var subitems = this.Directories
                               .Cast<IFileSystem>()
                               .Concat(this.Files);
            // aggregate and get the result
            var result = this.Depth < _AggregateThreshold
                // shown depth -> sequential aggregation
                ? subitems.Aggregate(_Accumulator.InitialValue, (c, t) => c.Add(t))
                // deeper -> parallel aggregation
                : subitems.AsParallel()
                          .Aggregate(
                              _Accumulator.InitialValue,
                              (c, t) => c.Add(t),
                              (x, y) => x.Add(y),
                              c => c);
            // save the result
            this._DirectoryCount = result.DirectoryCount;
            this._FileCount = result.FileCount;
            this._FileSize = result.FileSize;
            this.Scanned = DateTime.Now;
        }
        struct _Accumulator : IFileSystem
        {
            public int DirectoryCount   { get; private set; }
            public int FileCount        { get; private set; }
            public long FileSize        { get; private set; }
            public static _Accumulator InitialValue
            {
                get
                {
                    var i = new _Accumulator();
                    i.DirectoryCount = 1;
                    return i;
                }
            }
            public _Accumulator Add(IFileSystem operand)
            {
                this.DirectoryCount += operand.DirectoryCount;
                this.FileCount += operand.FileCount;
                this.FileSize += operand.FileSize;
                return this;
            }

            #region IFileSystem members (dummy implementation)
            public Guid Id { get { return default(Guid); } }
            public string Name { get { return null; } }
            public string FullName { get { return null; } }
            public Guid? ParentId { get { return null; } }
            public DateTime? Created { get { return null; } }
            public DateTime? Updated { get { return null; } }
            public string Owner { get { return null; } }
            public Guid SnapShotId { get { return default(Guid); } }
            public DateTime? Scanned { get { return null; } }
            #endregion
        }

        /// <summary>
        /// Clears directories/files lists, which could cost a lot of memory.
        /// </summary>
        public void Clear()
        {
            this._Directories.Clear();
            this._Files.Clear();
            this.IsCleared = true;
        }

        #endregion

        #region events

        /// <summary>
        /// Occurs when the scan of any instance of this class is beginning.
        /// </summary>
        public static event EventHandler DirectoryScanning;

        /// <summary>
        /// Occurs when the scan of any instance of this class is completed.
        /// </summary>
        public static event EventHandler DirectoryScanCompleted;
        
        #endregion
    }
}
